Random Music With Python

Posted on Fri 12 August 2016 in python

At PyCon 2016 I attended a very interesting session entitled Learning Python Through Music: JythonMusic & Pyknon, presented by Ria Baldevia. The talk was a great introduction to some python APIs that allow for the creation of music using the python language. Here I will demonstrate how to use the pyknon library, and some of the fun things one can do taking a programmatic approach to creating music.

pyknon

pyknon is an easy-to-use tool that allows one to generate music with python code. Individual "Note" objects contain all the information needed to create a note, including pitch, and duration.

In [2]:
from pyknon.music import Note

C_note = Note(0, 5, dur=0.25) 

Here, I have instantiated a Note object, setting its pitch to middle C, and its duration to a quarter note. There is also a Rest class that allows one to create a musical rest.

In [3]:
from pyknon.music import Rest

quarter_rest = Rest(0.25) # quarter rest

One can also string together a sequence of notes using the NoteSeq class.

In [4]:
from pyknon.music import NoteSeq

A_note = Note(9, 5, 0.25)

# create a sequence of notes and rests
seq = NoteSeq([C_note, A_note, quarter_rest, C_note]) 

We can now write this sequence of notes to a midi file.

Throughout this post, I will be converting midi files to mp3 files so that one can listen back through their browser. I use garage band for this.

In [5]:
from pyknon.genmidi import Midi

midi = Midi(1, tempo=120)
midi.seq_notes(seq, track=0)
midi.write("simple_noteseq.mid")
In [6]:
# I converted the midi file to a mp3 with garage band.
from IPython.display import Audio
Audio("simple_noteseq.mp3")
Out[6]:

So there you have it! A simple sequence of notes and rests that can be used to create individual music riffs. The pyknon API comes with many more cool features including the ability to create harmonies and chords. Though there is not a lot of documentation, the code is open source and very easy to read. There is also a book written by the author which describes the package in better detail.

When deciding how to utilize this tool, the first thing that came to mind was to randomly generate music. Specifically, I wanted to generate a sequence of randomly generated notes. I took the following approach to achieve this.

Given a set of pitches and a set of durations, randomly select a pitch, then a duration, and append it to an ongoing sequence of notes. For this exercise, I used the same octave for all notes.

The first step was to create a generic base track on top of which the randomly generated note sequences will sit. I used Garage Band to make a simple A power chord (5th chord) with an acoustic guitar preset. The generic nature of the power chord allows it to be paired with randomly generated note sequences in many different keys.

In [7]:
Audio("generic_background_in_A.mp3")
Out[7]:

Next, I'll define my set of durations.

Note that I will be using a different way of instantiating a NoteSeq object than outlined before. This alternative that I will demonstrate below allows one to specify the duration as an integer which represents the reciprocal of the duration. In other words, a value you of "2" will specify a half note, "4" a quarter note and so on. Also, note that I will leave out whole notes because I think they sound bad in this particular exercise.

In [8]:
durations = {
    "2", # half note
    "4", # quarter note
    "8", # eighth note
    "16" # sixteenth note
}

Next, define the set of pitches (the scale). I will first start off with a simple A major scale.

In [9]:
A_major = {
    'A',
    'B',
    'C#',
    'D',
    'E',
    'F#',
    'G#'
}

Then, for a given number of iterations, randomly sample one pitch and one duration to make a note (again all the same octave) then add that note to an ongoing sequence of notes.

In [10]:
import random
from pyknon.music import NoteSeq

def get_random_note_seq(n, pitches, durations, rests=True):
    # Add a rest to the set of pitches if desired.
    if rests:
        pitches.add('r')
        
    this_seq = ''
    for i in range(n):
        pitch = random.sample(pitches, 1)
        duration = random.sample(durations, 1)
        this_seq += pitch[0] + duration[0] + ' '

    return NoteSeq(this_seq)
In [11]:
from pyknon.genmidi import Midi

notes_A_major = get_random_note_seq(70, A_major, durations)
midi = Midi(1, tempo=139)
midi.seq_notes(notes_A_major, track=0)
midi.write("A_major.mid")

Finally, I took this randomly generated sequence of notes and played it over my generic guitar track show above. I also added the "EDM Bass : Breathless Pumping" preset to this track because... why not? The beauty is that every time I run this code I get a new song, but here is an example of one outcome.

In [12]:
Audio("key_A.mp3")
Out[12]:

I also tried this out in different keys (different modes with A as the base note). Here, I will define a few new sets of pitches for the different keys:

In [13]:
# A Dorian mode
G_major = {
    'G',
    'A',
    'B',
    'C',
    'D',
    'E',
    'F#'
}

# A Aeolian mode
D_major = {
    'D',
    'E',
    'F#',
    'G',
    'A',
    'B',
    'C#'
}

Next, I'll make a seperate track for each key.

In [14]:
notes_G_major = get_random_note_seq(70, G_major, durations)
midi = Midi(1, tempo=139)
midi.seq_notes(notes_G_major, track=0)
midi.write("G_major.mid")

notes_D_major = get_random_note_seq(70, D_major, durations)
midi = Midi(1, tempo=139)
midi.seq_notes(notes_D_major, track=0)
midi.write("D_major.mid")

Here is the result in G (A Dorian):

In [15]:
Audio("key_G.mp3")
Out[15]:

Now in the key of D (A Aeolian):

In [16]:
Audio("key_D.mp3")
Out[16]:

Ultimately, this exercise has been a fun way to observe how songs can "grow on you". The first time I listen to a randomly generated riff/melody, it does not sound too appealing, however, as I listen to it more, the riff becomes more familiar and more pleasing. I believe this is a result of a recognition and a musical preference for patterns. Though I have observed this phenomenon before, it was especially pronounced in this exercise as this computational approach is better at achieving randomness than a human would be.

In addition to this observation, a practical outcome for me has been to use this approach to generate melodies in my own music. Typically, I will generate a long sequence of notes, and from that sequence take a small snippet of notes that I believe would make a good melody riff.

To demonstrate both of these topics, I took the first 4 measures of the "key_A" random note sequence and repeated it over a background track. This track is still simple, but a little more interesting than the A power chords I was using before. I also added a garage band drum loop. Here is what I got:

In [17]:
Audio("simple_song.mp3")
Out[17]:

Now that this riff is just a small snippet of the larger random note sequence, it is much easier to establish a pattern, and therefore, the riff stands out as a catchy melody. I believe this is a big reason why this simple tune more closely resembles a pop song, though it must be noted that the addition of the extra tracks ("FM Piano" and "Almost Electric Beat") certainly contribute to a pop sound.

Whether one is a programmer trying to learn more about music, or a musician trying to learn more about programming, pyknon is a great tool to help you get started with your programmatically derived music projects.

In [ ]: